/*
 * @Author: lei
 * @Description: IO复用之epoll示例
 * @FilePath: /io/epoll.cpp
 */

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>

#include <iostream>
#include <unordered_set>
#include <vector>

#define exit_if(r, ...)                                            \
  if (r) {                                                         \
    printf(__VA_ARGS__);                                           \
    printf("error no: %d error msg %s\n", errno, strerror(errno)); \
    exit(1);                                                       \
  }

const int kReadEvent = EPOLLIN;
const int kWriteEvent = EPOLLOUT;
const int kMaxEvents = 20;
const int N = 2048;

// g++ -o epoll epoll.cpp -std=c++11 -g

class EchoServer {
 public:
  EchoServer(int ser_port) : ser_port_(ser_port), events_(kMaxEvents) {
    epfd_ = epoll_create(5);
    exit_if(epfd_ < 0, "epoll_create failed");

    ser_fd_ = socket(AF_INET, SOCK_STREAM, 0);
    exit_if(ser_fd_ < 0, "socket failed");
    SetNonBlock(ser_fd_);
    SetReuseaddr(ser_fd_);
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof addr);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(ser_port_);
    addr.sin_addr.s_addr = INADDR_ANY;
    int r = ::bind(ser_fd_, (struct sockaddr *)&addr, sizeof(struct sockaddr));
    exit_if(r, "bind to 0.0.0.0:%d failed %d %s", ser_port, errno,
            strerror(errno));
    r = listen(ser_fd_, 20);
    exit_if(r, "listen failed %d %s", errno, strerror(errno));
    printf("fd %d listening at %d\n", ser_fd_, ser_port);
    updateEvents(ser_fd_, kReadEvent, 0);

    // FIXME: epoll_wait返回-1,我裂开
  }
  ~EchoServer() {
    close(ser_fd_);
    updateEvents(ser_fd_, 0, 2);
    for (auto &fd : connfds_) {
      close(fd);
      updateEvents(fd, 0, 2);
    }
    close(epfd_);
  }
  void Loop(int waitms) {
    struct timespec timeout;
    timeout.tv_sec = waitms / 1000;
    timeout.tv_nsec = (waitms % 1000) * 1000 * 1000;
    while (1) {
      int n = epoll_wait(epfd_, &*events_.begin(), 2, -1);
      for (int i = 0; i < n; i++) {
        int fd = events_[i].data.fd;
        int temp_events = events_[i].events;
        if (temp_events & EPOLLIN) {
          if (fd == ser_fd_) {
            HandleConnection(fd, true);
          } else {
            HandleRead(fd, temp_events);
          }
        } else if (temp_events & EPOLLOUT) {
          HandleWrite(fd, temp_events);
        } else {
          exit_if(1, "unknown event");
        }
      }
    }
  }

 private:
  // modify:0表示ADD，1表示MOD,2表示DEL
  void updateEvents(int fd, int events, int index) {
    int ret;

    struct epoll_event event;
    bzero(&event, sizeof event);
    event.data.fd = fd;
    event.events = events;

    if (index == 0) {
      ret = epoll_ctl(epfd_, EPOLL_CTL_ADD, fd, &event);
      exit_if(ret == -1, "epoll_ctl failed!");
    } else if (index == 1) {
      ret = epoll_ctl(epfd_, EPOLL_CTL_MOD, fd, &event);
      exit_if(ret == -1, "epoll_ctl failed!");
    } else if (index == 2) {
      ret = epoll_ctl(epfd_, EPOLL_CTL_DEL, fd, &event);
      exit_if(ret == -1, "epoll_ctl failed!");
    }
  }
  void HandleConnection(int fd, bool flag) {
    if (flag) {
      struct sockaddr_in cliaddr;
      socklen_t addrlen = sizeof(cliaddr);
      int connfd = accept(ser_fd_, (struct sockaddr *)&cliaddr, &addrlen);
      exit_if(connfd < 0, "accept failed");
      printf("accept a connection from %s:%d\n", inet_ntoa(cliaddr.sin_addr),
             cliaddr.sin_port);
      SetNonBlock(connfd);
      updateEvents(connfd, kReadEvent, 0);
      connfds_.insert(connfd);
    } else {
      std::cout << "客户端断开，fd=" << fd << std::endl;
      updateEvents(fd, 0, 2);
      connfds_.erase(fd);
    }
  }

  void HandleRead(int fd, int events) {
    bzero(buf_recv_, sizeof buf_recv_);
    int n = ::read(fd, buf_recv_, sizeof buf_recv_);
    if (n == -1) {
      std::cout << "read error!" << std::endl;
      return;
    } else if (n == 0) {
      HandleConnection(fd, false);
      return;
    }
    std::copy(buf_recv_, buf_recv_ + N, buf_send_);
    std::cout << "read " << n << " bytes from fd " << fd << std::endl;
    updateEvents(fd, kReadEvent | kWriteEvent, 1);
    // 实际应用中，写出数据可能会返回EAGAIN，此时应当监听可写事件，当可写时再把数据写出
  }

  void HandleWrite(int fd, int events) {
    //实际应用应当实现可写时写出数据，无数据可写才关闭可写事件
    int ret = send(fd, buf_send_, sizeof buf_send_, 0);
    updateEvents(fd, kReadEvent & ~kWriteEvent, 1);
    bzero(buf_send_, sizeof buf_send_);
  }
  void SetNonBlock(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    exit_if(flags < 0, "fcntl failed");
    int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    exit_if(r < 0, "fcntl failed");
  }
  void SetReuseaddr(int fd) {
    int reuse = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof reuse);
  }

 private:
  char buf_send_[N];
  char buf_recv_[N];
  int ser_port_;
  int epfd_;
  int ser_fd_;
  std::vector<epoll_event> events_;
  std::unordered_set<int> connfds_;
};

int main() {
  EchoServer echo_server(10005);
  echo_server.Loop(10000);
  return 0;
}
// 在epfd对应的内核事件表上注册添加删除事件，而events_仅用来返回有事件的fd和相应事件